﻿using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using HandyControl.Data;

namespace HandyControl.Interactivity;

public class MouseDragElementBehavior : Behavior<FrameworkElement>
{
    public static readonly DependencyProperty ConstrainToParentBoundsProperty =
        DependencyProperty.Register("ConstrainToParentBounds", typeof(bool), typeof(MouseDragElementBehavior),
            new PropertyMetadata(ValueBoxes.FalseBox, OnConstrainToParentBoundsChanged));

    public static readonly DependencyProperty XProperty = DependencyProperty.Register("X", typeof(double),
        typeof(MouseDragElementBehavior), new PropertyMetadata(double.PositiveInfinity, OnXChanged));

    public static readonly DependencyProperty YProperty = DependencyProperty.Register("Y", typeof(double),
        typeof(MouseDragElementBehavior), new PropertyMetadata(double.PositiveInfinity, OnYChanged));

    // Fields
    private Transform _cachedRenderTransform;

    private Point _relativePosition;
    private bool _settingPosition;

    // Properties

    public bool ConstrainToParentBounds
    {
        get => (bool) GetValue(ConstrainToParentBoundsProperty);
        set => SetValue(ConstrainToParentBoundsProperty, ValueBoxes.BooleanBox(value));
    }

    private Rect ElementBounds
    {
        get
        {
            var layoutRect = ExtendedVisualStateManager.GetLayoutRect(AssociatedObject);
            return new Rect(new Point(0.0, 0.0), new Size(layoutRect.Width, layoutRect.Height));
        }
    }

    private FrameworkElement ParentElement =>
        AssociatedObject.Parent as FrameworkElement;

    private Transform RenderTransform
    {
        get
        {
            if (_cachedRenderTransform == null ||
                !ReferenceEquals(_cachedRenderTransform, AssociatedObject.RenderTransform))
            {
                var transform = CloneTransform(AssociatedObject.RenderTransform);
                RenderTransform = transform;
            }
            return _cachedRenderTransform;
        }
        set
        {
            if (!Equals(_cachedRenderTransform, value))
            {
                _cachedRenderTransform = value;
                AssociatedObject.RenderTransform = value;
            }
        }
    }

    private UIElement RootElement
    {
        get
        {
            DependencyObject associatedObject = AssociatedObject;
            for (var obj3 = associatedObject; obj3 != null; obj3 = VisualTreeHelper.GetParent(associatedObject))
                associatedObject = obj3;
            return associatedObject as UIElement;
        }
    }

    public double X
    {
        get =>
            (double) GetValue(XProperty);
        set => SetValue(XProperty, value);
    }

    public double Y
    {
        get =>
            (double) GetValue(YProperty);
        set => SetValue(YProperty, value);
    }

    // Events
    public event MouseEventHandler DragBegun;

    public event MouseEventHandler DragFinished;

    public event MouseEventHandler Dragging;

    // Methods
    private void ApplyTranslation(double x, double y)
    {
        if (ParentElement != null)
        {
            var point = TransformAsVector(RootElement.TransformToVisual(ParentElement), x, y);
            x = point.X;
            y = point.Y;
            if (ConstrainToParentBounds)
            {
                var parentElement = ParentElement;
                var rect = new Rect(0.0, 0.0, parentElement.ActualWidth, parentElement.ActualHeight);
                var transform2 = AssociatedObject.TransformToVisual(parentElement);
                var elementBounds = ElementBounds;
                var rect3 = transform2.TransformBounds(elementBounds);
                rect3.X += x;
                rect3.Y += y;
                if (!RectContainsRect(rect, rect3))
                {
                    if (rect3.X < rect.Left)
                    {
                        var num = rect3.X - rect.Left;
                        x -= num;
                    }
                    else if (rect3.Right > rect.Right)
                    {
                        var num2 = rect3.Right - rect.Right;
                        x -= num2;
                    }
                    if (rect3.Y < rect.Top)
                    {
                        var num3 = rect3.Y - rect.Top;
                        y -= num3;
                    }
                    else if (rect3.Bottom > rect.Bottom)
                    {
                        var num4 = rect3.Bottom - rect.Bottom;
                        y -= num4;
                    }
                }
            }
            ApplyTranslationTransform(x, y);
        }
    }

    internal void ApplyTranslationTransform(double x, double y)
    {
        var renderTransform = RenderTransform;
        var transform2 = renderTransform as TranslateTransform;
        if (transform2 == null)
        {
            var group = renderTransform as TransformGroup;
            var transform3 = renderTransform as MatrixTransform;
            if (group != null)
            {
                if (group.Children.Count > 0)
                    transform2 = group.Children[group.Children.Count - 1] as TranslateTransform;
                if (transform2 == null)
                {
                    transform2 = new TranslateTransform();
                    group.Children.Add(transform2);
                }
            }
            else
            {
                if (transform3 != null)
                {
                    var matrix = transform3.Matrix;
                    matrix.OffsetX += x;
                    matrix.OffsetY += y;
                    var transform4 = new MatrixTransform
                    {
                        Matrix = matrix
                    };
                    RenderTransform = transform4;
                    return;
                }
                var group2 = new TransformGroup();
                transform2 = new TranslateTransform();
                if (renderTransform != null)
                    group2.Children.Add(renderTransform);
                group2.Children.Add(transform2);
                RenderTransform = group2;
            }
        }
        transform2.X += x;
        transform2.Y += y;
    }

    internal static Transform CloneTransform(Transform transform)
    {
        if (transform == null)
            return null;
        if (transform is ScaleTransform transform2)
            return new ScaleTransform
            {
                CenterX = transform2.CenterX,
                CenterY = transform2.CenterY,
                ScaleX = transform2.ScaleX,
                ScaleY = transform2.ScaleY
            };
        if (transform is RotateTransform transform3)
            return new RotateTransform
            {
                Angle = transform3.Angle,
                CenterX = transform3.CenterX,
                CenterY = transform3.CenterY
            };
        if (transform is SkewTransform transform4)
            return new SkewTransform
            {
                AngleX = transform4.AngleX,
                AngleY = transform4.AngleY,
                CenterX = transform4.CenterX,
                CenterY = transform4.CenterY
            };
        if (transform is TranslateTransform transform5)
            return new TranslateTransform
            {
                X = transform5.X,
                Y = transform5.Y
            };
        if (transform is MatrixTransform transform6)
            return new MatrixTransform { Matrix = transform6.Matrix };
        if (!(transform is TransformGroup group))
            return null;
        var group2 = new TransformGroup();
        foreach (var transform12 in group.Children)
            group2.Children.Add(CloneTransform(transform12));
        return group2;
    }

    internal void EndDrag()
    {
        AssociatedObject.MouseMove -= OnMouseMove;
        AssociatedObject.LostMouseCapture -= OnLostMouseCapture;
        AssociatedObject.RemoveHandler(UIElement.MouseLeftButtonUpEvent,
            new MouseButtonEventHandler(OnMouseLeftButtonUp));
    }

    private static Point GetTransformOffset(GeneralTransform transform)
    {
        return transform.Transform(new Point(0.0, 0.0));
    }

    internal void HandleDrag(Point newPositionInElementCoordinates)
    {
        var x = newPositionInElementCoordinates.X - _relativePosition.X;
        var y = newPositionInElementCoordinates.Y - _relativePosition.Y;
        var point = TransformAsVector(AssociatedObject.TransformToVisual(RootElement), x, y);
        _settingPosition = true;
        ApplyTranslation(point.X, point.Y);
        UpdatePosition();
        _settingPosition = false;
    }

    protected override void OnAttached()
    {
        AssociatedObject.AddHandler(UIElement.MouseLeftButtonDownEvent,
            new MouseButtonEventHandler(OnMouseLeftButtonDown), false);
    }

    private static void OnConstrainToParentBoundsChanged(object sender, DependencyPropertyChangedEventArgs args)
    {
        var behavior = (MouseDragElementBehavior) sender;
        behavior.UpdatePosition(new Point(behavior.X, behavior.Y));
    }

    protected override void OnDetaching()
    {
        AssociatedObject.RemoveHandler(UIElement.MouseLeftButtonDownEvent,
            new MouseButtonEventHandler(OnMouseLeftButtonDown));
    }

    private void OnLostMouseCapture(object sender, MouseEventArgs e)
    {
        EndDrag();
        DragFinished?.Invoke(this, e);
    }

    private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        StartDrag(e.GetPosition(AssociatedObject));
        DragBegun?.Invoke(this, e);
    }

    private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        AssociatedObject.ReleaseMouseCapture();
    }

    private void OnMouseMove(object sender, MouseEventArgs e)
    {
        HandleDrag(e.GetPosition(AssociatedObject));
        Dragging?.Invoke(this, e);
    }

    private static void OnXChanged(object sender, DependencyPropertyChangedEventArgs args)
    {
        var behavior = (MouseDragElementBehavior) sender;
        behavior.UpdatePosition(new Point((double) args.NewValue, behavior.Y));
    }

    private static void OnYChanged(object sender, DependencyPropertyChangedEventArgs args)
    {
        var behavior = (MouseDragElementBehavior) sender;
        behavior.UpdatePosition(new Point(behavior.X, (double) args.NewValue));
    }

    private static bool RectContainsRect(Rect rect1, Rect rect2)
    {
        if (rect1.IsEmpty || rect2.IsEmpty)
            return false;
        return rect1.X <= rect2.X && rect1.Y <= rect2.Y && rect1.X + rect1.Width >= rect2.X + rect2.Width &&
               rect1.Y + rect1.Height >= rect2.Y + rect2.Height;
    }

    internal void StartDrag(Point positionInElementCoordinates)
    {
        _relativePosition = positionInElementCoordinates;
        AssociatedObject.CaptureMouse();
        AssociatedObject.MouseMove += OnMouseMove;
        AssociatedObject.LostMouseCapture += OnLostMouseCapture;
        AssociatedObject.AddHandler(UIElement.MouseLeftButtonUpEvent,
            new MouseButtonEventHandler(OnMouseLeftButtonUp), false);
    }

    private static Point TransformAsVector(GeneralTransform transform, double x, double y)
    {
        var point = transform.Transform(new Point(0.0, 0.0));
        var point2 = transform.Transform(new Point(x, y));
        return new Point(point2.X - point.X, point2.Y - point.Y);
    }

    private void UpdatePosition()
    {
        var transformOffset = GetTransformOffset(AssociatedObject.TransformToVisual(RootElement));
        X = transformOffset.X;
        Y = transformOffset.Y;
    }

    private void UpdatePosition(Point point)
    {
        if (!_settingPosition && AssociatedObject != null)
        {
            var transformOffset = GetTransformOffset(AssociatedObject.TransformToVisual(RootElement));
            var x = double.IsNaN(point.X) ? 0.0 : point.X - transformOffset.X;
            var y = double.IsNaN(point.Y) ? 0.0 : point.Y - transformOffset.Y;
            ApplyTranslation(x, y);
        }
    }
}
